<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="X-UA-Compatible" content="IE=Edge;chrome=1"/>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
	<title>jseval</title>

	<!-- jQuery by John Resig, MIT License, http://jquery.com/ -->
    <script src="jquery.min-1.7.1.js"></script>

	<!-- jStorage by Andris Reinman, MIT-styled license, http://www.jstorage.info/ -->
    <script src="jstorage.min-0.1.6.1.js" type="text/javascript"></script>

	<!-- splitter by Kristaps Kukurs, MIT license, http://krikus.com/js/splitter/ -->
    <script src="splitter.min-1.0.js" type="text/javascript"></script>

	<!-- Benchmark.js by Mathias Bynens and John-David Dalton, MIT license, http://benchmarkjs.com/ -->
    <script src="benchmark.min-0.3.0.js" type="text/javascript"></script>

	<!-- CodeMirror by marijnh, MIT-style license, http://codemirror.net/ -->
    <link href="CodeMirror/lib/codemirror.css" type="text/css" rel="stylesheet">
    <script src="CodeMirror/lib/codemirror.js" type="text/javascript"></script>
    <script src="CodeMirror/mode/xml/xml.js"></script>
    <script src="CodeMirror/mode/javascript/javascript.js"></script>
    <script src="CodeMirror/mode/css/css.js"></script>
    <script src="CodeMirror/mode/htmlmixed/htmlmixed.js"></script>
    <script src="CodeMirror/lib/util/dialog.js" type="text/javascript"></script>
    <script src="CodeMirror/lib/util/searchcursor.js" type="text/javascript"></script>
    <script src="CodeMirror/lib/util/search.js" type="text/javascript"></script>
    <script src="CodeMirror/lib/util/formatting.js" type="text/javascript"></script>
    <script src="CodeMirror/lib/util/simple-hint.js" type="text/javascript"></script>
    <link href="CodeMirror/lib/util/simple-hint.css" type="text/css" rel="stylesheet">
    <script src="CodeMirror/lib/util/javascript-hint.js" type="text/javascript"></script>

<style>

	html, body {
		height:100%;
		width:100%;
		margin:0;
		padding:0;
		overflow: hidden;
		font-size: 10pt;
	}

	#editor,
	#panel {
		float: left;
		height: 100%;
	}

	#editor {
		width:65%;
	}

	#panel {
		width:35%;
		overflow: hidden;
	}


	#output {
		font-family: monospace;
		color: #EEE;
	}

	#html {
		border-style: none;
		width: 100%;
		height: 100%;

		/* workaround to fix the issue with the height:100%. see also .splitbarH height */
		-webkit-box-sizing: border-box;
		-moz-box-sizing: border-box;
		-ms-box-sizing: border-box;
		-o-box-sizing: border-box;
		box-sizing: border-box;

		padding-bottom: 6px !important;
		/* */
	}

	#misc {
		white-space: pre;
		background-color: #f0fff0;
		color: #000;
	}

	#error {
		font-weight: bold;
		white-space: -moz-pre-wrap;
		background-color: red;
		color: white;
	}

	#text {
		display: inline;
		white-space: pre;
		background-color: #f0f0ff;
		color: #0000ff;
	}

	#eval {
		display: inline;
		white-space: pre;
		background-color: #f4f4f4;
		color: #000;
	}

	#input {
		display: block;
		width: 100%;
		height: 0px;
		padding: 0px;
		margin: 0px;
		border-width: 0px;
		outline: none;
		overflow: auto;
		resize: none;
	}


/* scrollbars */

	::-webkit-scrollbar {
		width: 8px;
		height: 8px;
	}
	::-webkit-scrollbar-thumb {
		-webkit-border-radius: 4px;
		border: 1px solid #fff;
		background-color: #eee;
	}


/* editor */

	#editor .CodeMirror {
		height: 100%;
	}

	#editor .CodeMirror .CodeMirror-scroll {
		height: 100%;
	}

	#editor .CodeMirror .activeline {
		background-color: rgba(255,255,0, 0.05);
	}

	#editor .CodeMirror-gutter {
		cursor: default;
	}

	#editor .CodeMirror .cm-tab:after {
		content: "\21e5";
		position: relative;
		left: -0.1em;
		color: rgba(0,0,0, 0.05);
	}

	#editor .CodeMirror	.CodeMirror-matchingbracket {
		color: #0B0;
	}

	#editor .CodeMirror	.CodeMirror-nonmatchingbracket {
		color: #D00;
	}


/* splitter */

	.splitbarV {
		float: left;
		width: 6px;
		height: 100%;
	}

	.splitbarH {
		height: 6px;
		width: 100%;
	}

	.splitbarV,	.splitbarH {
		background-color: #ddd;
	}

	.splitbarH:hover, .splitbarV:hover {
		background-color: #bbb;
	}


/* context menu */
	.contextMenu {
		font-family: sans-serif;
		position: absolute;
		z-index: 99999;
		width: auto;
		padding: 0px;
		margin: 0px;
		display: none;
		border: solid 1px #ccc;
		background: #eee;
		box-shadow: 2px 2px 3px rgba(0,0,0,0.4);
		cursor: default;
	}

	.contextMenu li {
		list-style: none;
		color: #333;
		padding: 1px 10px 1px 5px;
		height: 20px;
	}

	.contextMenu li a {
		padding-left: 16px;
	}

	.contextMenu li input:first-child {

		width: 16px;
		margin: 0px;
		vertical-align: text-bottom;
	}

	.contextMenu li:hover {
		color: #fff;
		background-color: #39f;
	}

	.contextMenu li:active {
		background-color: #000;
	}

	.contextMenu li.disabled {
		color: #bbb;
		background-color: transparent;
	}

	.contextMenu li.separator {
		height: 0px;
		border-top: solid 1px #ccc;
		background-color: transparent;
	}

	.contextMenu a {
		font-family: inherit;
		color: inherit;
		cursor: inherit;
		display: block;
	}



</style>
<script language="javascript" type="text/javascript"><!--

window.print = function(){};

jQuery.fn.appendText = function(text) {

	this.text(this.text() + text);
};


// context menu

jQuery.fn.contextMenu = function(menu, callback) {

	$(this).on('contextmenu', function(ev) {

		ev.preventDefault();
		$('.contextMenu:visible').fadeOut(0);
		$(document).one('mousedown', function() {

			$(menu).fadeOut(0).off('click');
		});
		$(menu).css({ left: ev.pageX, top: ev.pageY }).fadeIn('fast').on('mousedown', false).on('click', function(ev) {

			var li = $(ev.target).parentsUntil(menu);
			if ( li.hasClass('disabled') )
				return;
			$(menu).off('click').fadeOut().add(document).off('mousedown');
			callback(li);
		});
	});
	return $(this);
};


//

(function() {


	function Conductor(initialState) {

		var state = {};

		var listeners = {};

		this.getState = function(stateName) {

			return state[stateName];
		}


		this.triggerEvent = function(eventName, data) {

			var handlers = listeners[stateName];
			if ( handlers ) {

				var len = handlers.length;
				for ( var i = 0; i < len; ++i )
					handlers[i].call(this, eventName, data);
			}

		}

		this.triggerState = function(stateName, value) {

			console.log(stateName, '=', String(value).replace('\n', ' '));

			var prev = state[stateName];
			state[stateName] = value;

			var handlers = listeners[stateName];
			if ( handlers ) {

				var len = handlers.length;
				for ( var i = 0; i < len; ++i )
					handlers[i].call(this, stateName, value, prev);
			}
		}

		this.listen = function(names, handler) {

			var nameList = names.split(' ');
			var len = nameList.length;
			for ( var i = 0; i < len; ++i ) {

				var name = nameList[i];
				(listeners[name] || (listeners[name] = [])).push(handler);
			}
		}

		this.init = function() {

			for ( var stateName in initialState ) {

				this.triggerState(stateName, initialState[stateName]);
			}
		}
	}

	var cond = new Conductor({
		autoEval: true,
		printHTML: false,
		page: 'page1'
	});



	function screenInfo(text) {

		$('<div>').css({'position':'absolute', 'width':'100%', 'height':'100%', 'background-color':'#eee', 'color':'#000', 'textShadow':'#000 0px 0px 30px', 'font-size':'20em', 'text-align':'center' }).appendTo('body').text(text).fadeOut('fast', function() { $(this).remove() });
	}

	function blink() {

		$('<div>').css({'position':'absolute', 'width':'100%', 'height':'100%', 'opacity':'0.25', 'background-color':'#000'}).appendTo('body').fadeOut('fast', function() { $(this).remove() });
	}

	function storageEffect(save) {

		var tmp = $('<div>').css({'position':'absolute', 'width':'100%', 'height':'100%', 'border-bottom':'10px solid #aaa', 'border-right':'10px solid #aaa'}).hide().appendTo('body');
		if ( save )
			tmp.show().hide('fast', function() { $(this).remove() });
		else
			tmp.show('fast', function() { $(this).remove() });
	}

	function numSplit(num) {

		return String(num).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,');
	}

	function isHtmlText(text) {

		return /^\s*</.test(text) && />\s*$/.test(text);
	}

	function showHelp() {

		$('#misc').text('Help:\n F3 / Shift-F3: Search forward/backward\n Ctrl-Space: autocompletion\n Ctrl-S / Ctrl-L: save/load\n Ctrl-P: profile selected code\n Alt-1 to Alt-6: buffer 1-6');
	}


	var editor;

	$(window).ready(function() {

		$('#panel').splitter({ splitHorizontal:true, A:$('#input'), B:$('#output'), closeableto:0, animSpeed:150 });
		$('body').splitter({ A:$('#editor'), B:$('#panel'), closeableto:0, animSpeed:150 });

		$('#input').attr('spellcheck', 'false').on('input change', function() {

			cond.triggerState('input', $(this).val());
		});

		$('#panel')
			.on('dragover', false)
			.on('drop', function(e) {

				var file = e.originalEvent.dataTransfer.files[0];
				if ( file ) {

					var reader = new FileReader();
					reader.onload = function(evt) {

						$('#input').val(reader.result).trigger('change');
					}
					reader.readAsText(file);
					e.preventDefault();
				}
			});

		var updateDelay;
		var hlLine;

		var qSearchText = '';
		function quickSearch(reverse) {

			if ( editor.somethingSelected() ) {

				qSearchText = editor.getSelection();
			}

			if ( !qSearchText )
				return;

			var qSearch = editor.getSearchCursor(qSearchText, editor.getCursor(reverse), true);

			if ( qSearch && qSearch.find(reverse) ) {

				editor.setSelection(qSearch.pos.from, qSearch.pos.to);
			} else {

				blink();
			}
		}

		editor = CodeMirror($('#editor').get(0), {
			mode: 'text/plain',
			undoDepth: 100,
			matchBrackets: true,
			lineNumbers: true,
			tabSize: 2,
			indentUnit: 2,
			indentWithTabs: true,
			electricChars:true,
			fixedGutter:true,
			onChange: function() {

				clearTimeout(updateDelay);
				updateDelay = setTimeout(function() {

					cond.triggerState('source', editor.getValue());
				});
			},
			onGutterClick: function(cm, line) {

				editor.setSelection({line:line, ch:0}, {line:line});
			},
			onCursorActivity: function() {

				var cursor = editor.getCursor();
				editor.setLineClass(hlLine, null);
				hlLine = editor.setLineClass(cursor.line, "activeline");
			},
			onKeyEvent: function(cm, event) {

				return (event.shiftKey && event.keyCode == 45); // workaround to make Shift-Insert working.
			},
			extraKeys: {
				'Insert': function() {
					//editor.toggleOverwrite();
				},
				'F1': function() {

					showHelp();
				},
				'F3': function() {

					quickSearch(false);
				},
				'Shift-F3': function() {

					quickSearch(true);
				},
				'Ctrl-Space': function() {

					CodeMirror.simpleHint(editor, CodeMirror.javascriptHint);
				},
				'Ctrl-P': function() {

					cond.triggerState('profile', [editor.getValue(), editor.indexFromPos(editor.getCursor(true)), editor.indexFromPos(editor.getCursor(false))]);
				},
				'Ctrl-S': function() {

					saveState();
					storageEffect(true);
				},
				'Ctrl-L': function() {

					storageEffect(false);
					loadState();
				},
				'Ctrl-I': function() {

					editor.autoIndentRange(editor.getCursor(true), editor.getCursor(false));
				},
				'Ctrl-F': function() {

					editor.autoFormatRange(editor.getCursor(true), editor.getCursor(false));
				},
				'Alt-1': cond.triggerState.bind(cond, 'page', 'page1'),
				'Alt-2': cond.triggerState.bind(cond, 'page', 'page2'),
				'Alt-3': cond.triggerState.bind(cond, 'page', 'page3'),
				'Alt-4': cond.triggerState.bind(cond, 'page', 'page4')
			}
		});

		hlLine = editor.setLineClass(editor.getCursor().line, 'activeline');


		$('#editor').contextMenu('#editorContextMenu', function(li) {

			var cmd = li.attr('id')||'';
			switch (cmd) {
			case 'cmd_eval':
				cond.triggerState('eval', editor.getValue());
				break;
			case 'profile':
				cond.triggerState('profile', [editor.getValue(), editor.indexFromPos(editor.getCursor(true)), editor.indexFromPos(editor.getCursor(false))]);
				break;
			case 'clear':
				editor.setValue('');
				break;
			}

			if ( cmd.substr(0, 4) == 'page' ) {

				cond.triggerState('page', cmd);
			}
		});


		$('#panel').contextMenu('#panelContextMenu', function(li) {

			var cmd = li.attr('id')||'';
			switch (cmd) {
			case 'help':
				showHelp();
				break;
			case 'clear':
				$('#misc, #eval, #text, #error').empty();
				break;
			}
		});

		cond.listen('autoEval', function(stateName) {

			$('#autoeval').prop('checked', this.getState(stateName));
		});

		$('#autoeval').change(function() {

			cond.triggerState('autoEval', $(this).prop('checked'));
		});

		cond.listen('printHTML', function(stateName) {

			$('#printhtml').prop('checked', this.getState(stateName));
		});

		$('#printhtml').change(function() {

			cond.triggerState('printHTML', $(this).prop('checked'));
		});

		cond.init();
		editor.focus();
	});

	cond.listen('profile', function(stateName) {

		var value = this.getState(stateName);
		var script = value[0];
		var start = value[1];
		var end = value[2];

		cond.triggerState('misc', 'Profiling...');

		if ( start != end ) {

			var before = script.substring(0, start);
			var inside = script.substring(start, end);
			var after = script.substring(end);

			// TBD: make the difference between an expression and a block
			cond.triggerState('eval', before + ' ((function() { function $$$() { return ' + inside + ' }; profile($$$); return $$$() })()) ' + after + ';""');
		} else {

			cond.triggerState('eval', 'profile(function() {' + script + '});"";');
		}

	});

	cond.listen('source', function(stateName) {

		var isHTML = isHtmlText(this.getState(stateName));
		editor.setOption('mode', isHTML ? 'htmlmixed' : 'javascript');
		if ( this.getState('autoEval') )
			cond.triggerState('eval', this.getState('source'));
	});

	cond.listen('input', function(stateName) {

		cond.triggerState('eval');
	});

	cond.listen('eval', function(stateName) {

		var data = this.getState(stateName);
		var isHTML = isHtmlText(data);
		if ( isHTML ) {

			cond.triggerState('outputHTML', data);
			cond.triggerState('misc', null);
			cond.triggerState('outputTEXT', null);
			cond.triggerState('print', null);
			cond.triggerState('error', null);
		} else {

			cond.triggerState('outputHTML', null);
			cond.triggerState('misc', null);

//			code = code.replace(/\{\{(.*?)\}\}/, function(t, page) {
//
//				return $.jStorage.get(storageKey(page, 'code'), '');
//			});

			var printOutput = [];
			try {

				var cx = {
//				getPage: function(page) {
//
//					return $.jStorage.get(storageKey(page, 'code'), '');
//				},

					input: function() {

						return cond.getState('input');
					},
					print: function() {

						for ( var i = 0; i < arguments.length; ++i ) {

							printOutput.push(String(arguments[i]));
						}
					},
					profile: function(fct) {

						Benchmark({
							fn: fct,
							maxTime:4,
							async:true,
							onCycle:function() {
								cond.triggerState('misc', 'Profile:\n  ' + numSplit(this.hz.toFixed(0)) + ' hz');
							},
							onComplete:function() {
								cond.triggerState('misc', 'Profile:\n  <span title="'+((1000/this.hz).toFixed(2))+' ms">' + numSplit(this.hz.toFixed(0)) + ' hz</span> <small>&plusmn;</small>' + this.stats.rme.toFixed(1) +'%');
							}
						}).run();
					}
				}

				var lastExpression, lineNumber = new Error().lineNumber;
				with (cx) lastExpression = eval(data);

				cond.triggerState('outputTEXT', lastExpression);
				cond.triggerState('error', null);
			} catch (ex) {

				cond.triggerState('outputTEXT', null);
				cond.triggerState('error', (lineNumber != undefined ? 'line ' + (ex.lineNumber - lineNumber) + ': ' : '' ) + ex);
			}

			cond.triggerState('print', printOutput.join(''));
		}
	});

	cond.listen('misc', function(stateName) {

		if ( this.getState(stateName) == null )
			$('#misc').hide().text('');
		else
			$('#misc').html(this.getState(stateName)).show();
	});

	cond.listen('error', function(stateName) {

		if ( this.getState(stateName) == null )
			$('#error').hide().text('');
		else
			$('#error').text(this.getState(stateName)).show();
	});

	cond.listen('print printHTML', function(stateName) {

		if ( this.getState('printHTML') )
			$('#text').html(this.getState('print')||'').show();
		else
			$('#text').text(this.getState('print')||'').show();
	});

	cond.listen('outputHTML', function(stateName) {

		var html = this.getState(stateName);
		if ( html == null )
			$('#html').hide();
		else
			$('#html').show();
		$('#html').contents().each(function() {

			this.open();
			this.write(html||'');
			this.close();
		});
	});

	cond.listen('outputTEXT', function(stateName) {

		if ( this.getState(stateName) == null )
			$('#misc, #eval, #text, #error').hide().empty();
		else
			$('#eval').show().text(this.getState(stateName) + '\n');
	});

	cond.listen('page', function(stateName) {

//			$.jStorage.set('jseval'+'/page', pageName);
//			saveState();
//			loadState();

/*

	function storageKey(page, name) {

		return 'jseval'+'/'+page+'/'+name;
	}

	function saveState() {

		$.jStorage.set(storageKey(currentPage, 'code'), editor.getValue());
		$.jStorage.set(storageKey(currentPage, 'input'), $('#input').val());
		$.jStorage.set(storageKey(currentPage, 'selStart'), editor.getCursor(true));
		$.jStorage.set(storageKey(currentPage, 'selEnd'), editor.getCursor(false));
	}

	function loadState() {

		editor.setValue($.jStorage.get(storageKey(currentPage, 'code'), ''));
		$('#input').val($.jStorage.get(storageKey(currentPage, 'input'), ''));
		var selStart = $.jStorage.get(storageKey(currentPage, 'selStart'));
		if ( selStart ) {

			editor.setSelection(selStart, $.jStorage.get(storageKey(currentPage, 'selEnd')));
		}
	}
*/
		screenInfo(this.getState('page'));
	});


})();

--></script>
</head>
<body>
	<div id="editor"></div>
	<div id="panel">
		<textarea id="input" rows="1" placeholder="get this content with input()"></textarea>
		<div id="output">
			<div id="misc"></div>
			<div id="eval"></div>
			<div id="text"></div>
			<div id="error"></div>
			<iframe id="html"></iframe>
		</div>
	</div>
	<ul id="editorContextMenu" class='contextMenu'>
		<li><input id="autoeval" type="checkbox"/><label for="autoeval">auto-eval</label></li>
		<li id="cmd_eval"><a>eval</a></li>
		<li id="profile" title="Ctrl-P"><a>profile</a></li>
		<li class="separator"></li>
		<li id="clear"><a>clear</a></li>
		<li id="page1"><a>show page1</a></li>
		<li id="page2"><a>show page2</a></li>
		<li id="page3"><a>show page3</a></li>
		<li id="page4"><a>show page4</a></li>
		<li id="page5"><a>show page5</a></li>
		<li id="page6"><a>show page6</a></li>
	</ul>
	<ul id="panelContextMenu" class='contextMenu'>
		<li><input id="printhtml" type="checkbox"/><label for="printhtml">HTML</label></li>
		<li id="clear"><a>clear</a></li>
		<li class="separator"></li>
		<li id="help" title="F1"><a>help</a></li>
	</ul>
</body>
